﻿/*
 * This code is an extender of JSMin project
 * http://www.crockford.com/javascript/jsmin.html 
 * Please give credit to Douglas Crockford
 * 
 * Haiyan Du, May 22nd, 2009
 * */

using System;
using System.IO;

namespace JavaScriptSupport
{
    class JavaScriptMinifier
    {
        const int EOF = -1;
        StreamReader sr;
        StreamWriter sw;
        int theA;
        int theB;
        int theLookahead = EOF;
       
        /// <summary>
        /// Show command line help
        /// </summary>
        static void Help()
        {
            Console.WriteLine(@"Syntax:
  JSCompress [/options] 

    /?                  Show help  
    /debug              Debug build, compression is suspend
    /release            Release build, compression enabled    
    /f                  Force compression in any Configuratin Mode, event in debug mode.    
    /i:<dir or file>    Specify input folder or file 
    /o:<dir or file>    Specify output folder or file
    /s:<Search pattern> Specify search pattern for JavaScript Files, such as *.js
   

Setup Visual Studio Prebuild Event:
(Dir)JSCompress  /$(ConfigurationName)  /o:$(ProjectDir)JavaScriptFolder 
            /i:$(ProjectDir)JavaScriptDevelopmentFolder /s:*.js

$(ConfigurationName)= Build Mode in your VS,possible value: Debug,Release,...
$(ProjectDir) = Project Directory
");  
        }
        static void Main(string[] args)
        {
            bool compress = true;
            bool forcecompress = false;
            string searchPattern = "*.js";
            string outputF = "";
            string inputF = "";
            bool compressFolder = true;
            if (args.Length < 1)
            {
                Help();
                return;
            }
            foreach (string arg in args)
            {
               if(arg=="/?" || arg=="/h")
                {
                        Help();
                        return;
                }
                else if(arg.ToLower()=="/debug")
                    compress = false;
                else if(arg=="/f")
                    forcecompress = true;
               else if (arg.ToLower().StartsWith("/s:"))
                     searchPattern = arg.ToLower().Substring(3);
               else if (arg.ToLower().StartsWith("/i:"))
                   inputF = arg.ToLower().Substring(3);
               else if (arg.ToLower().StartsWith("/o:"))
                   outputF = arg.ToLower().Substring(3);
               else
               {
                   ;
               }
            }
            if (File.Exists(inputF))
                compressFolder = false;
            if (forcecompress)
                compress = true;
            try
            {
                #region ""
                if (compressFolder)
                {
                    //compress folder
                    FileInfo[] sourceFiles =
                        new DirectoryInfo(inputF).GetFiles(searchPattern, SearchOption.AllDirectories);
                    foreach (FileInfo f in sourceFiles)
                    {
                        string infile = f.FullName;
                        int totalchar = inputF.Length;
                        string subfilename = infile.Substring(totalchar);
                        string outfile = "";
                        if (outputF.EndsWith("\\"))
                        {
                            outfile = outputF + subfilename;
                        }
                        else
                        {
                            outfile = outputF + "\\" + subfilename;
                        }

                        CheckFolder(outfile);
                        if (File.Exists(outfile))
                            File.Delete(outfile);
                        if (compress)
                        {
                            new JavaScriptMinifier().Minify(infile, outfile);
                            Console.WriteLine("Finished:" + infile);
                        }
                        else
                        {
                            File.Copy(infile, outfile, true);
                            Console.WriteLine("Copied:" + infile);
                        }
                    }
                }
                else
                {

                    //compress file
                    CheckFolder(outputF);
                    if (File.Exists(outputF))
                        File.Delete(outputF);
                    if (compress)
                    {
                        new JavaScriptMinifier().Minify(inputF, outputF);
                        Console.WriteLine("Compressed:" + inputF);
                    }
                    else
                    {
                        File.Copy(inputF, outputF, true);
                        Console.WriteLine("Copied:" + inputF);
                    }
                }
                #endregion
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());

            }
                

        }  
      
        /// <summary>
        /// Compress JavaScript file from source to destination
        /// </summary>
        /// <param name="src"></param>
        /// <param name="dst"></param>
        public void Minify(string src, string dst)
        {
            using (sr = new StreamReader(src))
            {
                using (sw = new StreamWriter(dst))
                {
                    jsmin();
                }
            }
        }

        #region "Private Functions"
        /// <summary>
        /// Check if the file directory is exist or not.
        /// if not exist, create one.
        /// </summary>
        /// <param name="filename"></param>
        private static void CheckFolder(string filename)
        {
            try
            {
                FileInfo f = new FileInfo(filename);
                if (!f.Directory.Exists)
                {
                    f.Directory.Create();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }    
        /// <summary>
        /// Copy the input to the output, deleting the characters which are
        ///        insignificant to JavaScript. Comments will be removed. Tabs will be
       ///         replaced with spaces. Carriage returns will be replaced with linefeeds.
        ///        Most spaces and linefeeds will be removed.
        /// </summary>
        void jsmin()
        {
            theA = '\n';
            action(3);
            while (theA != EOF)
            {
                switch (theA)
                {
                    case ' ':
                        {
                            if (isAlphanum(theB))
                            {
                                action(1);
                            }
                            else
                            {
                                action(2);
                            }
                            break;
                        }
                    case '\n':
                        {
                            switch (theB)
                            {
                                case '{':
                                case '[':
                                case '(':
                                case '+':
                                case '-':
                                    {
                                        action(1);
                                        break;
                                    }
                                case ' ':
                                    {
                                        action(3);
                                        break;
                                    }
                                default:
                                    {
                                        if (isAlphanum(theB))
                                        {
                                            action(1);
                                        }
                                        else
                                        {
                                            action(2);
                                        }
                                        break;
                                    }
                            }
                            break;
                        }
                    default:
                        {
                            switch (theB)
                            {
                                case ' ':
                                    {
                                        if (isAlphanum(theA))
                                        {
                                            action(1);
                                            break;
                                        }
                                        action(3);
                                        break;
                                    }
                                case '\n':
                                    {
                                        switch (theA)
                                        {
                                            case '}':
                                            case ']':
                                            case ')':
                                            case '+':
                                            case '-':
                                            case '"':
                                            case '\'':
                                                {
                                                    action(1);
                                                    break;
                                                }
                                            default:
                                                {
                                                    if (isAlphanum(theA))
                                                    {
                                                        action(1);
                                                    }
                                                    else
                                                    {
                                                        action(3);
                                                    }
                                                    break;
                                                }
                                        }
                                        break;
                                    }
                                default:
                                    {
                                        action(1);
                                        break;
                                    }
                            }
                            break;
                        }
                }
            }
        }
        /// <summary>
        ///         ///action -- do something! What you do is determined by the argument:
        ///        1   Output A. Copy B to A. Get the next B.
        ///        2   Copy B to A. Get the next B. (Delete A).
        ///        3   Get the next B. (Delete B).
        ///   action treats a string as a single character. Wow!
        ///   action recognizes a regular expression if it is preceded by ( or , or =.
        ///*/
        ///       ////
        /// </summary>
        /// <param name="d"></param>
        void action(int d)
        {
            if (d <= 1)
            {
                put(theA);
            }
            if (d <= 2)
            {
                theA = theB;
                if (theA == '\'' || theA == '"')
                {
                    for (; ; )
                    {
                        put(theA);
                        theA = get();
                        if (theA == theB)
                        {
                            break;
                        }
                        if (theA <= '\n')
                        {
                            throw new Exception(string.Format("Error: JSMIN unterminated string literal: {0}\n", theA));
                        }
                        if (theA == '\\')
                        {
                            put(theA);
                            theA = get();
                        }
                    }
                }
            }
            if (d <= 3)
            {
                theB = next();
                if (theB == '/' && (theA == '(' || theA == ',' || theA == '=' ||
                                    theA == '[' || theA == '!' || theA == ':' ||
                                    theA == '&' || theA == '|' || theA == '?' ||
                                    theA == '{' || theA == '}' || theA == ';' ||
                                    theA == '\n'))
                {
                    put(theA);
                    put(theB);
                    for (; ; )
                    {
                        theA = get();
                        if (theA == '/')
                        {
                            break;
                        }
                        else if (theA == '\\')
                        {
                            put(theA);
                            theA = get();
                        }
                        else if (theA <= '\n')
                        {
                            throw new Exception(string.Format("Error: JSMIN unterminated Regular Expression literal : {0}.\n", theA));
                        }
                        put(theA);
                    }
                    theB = next();
                }
            }
        }    
      
        int next()
        {
            int c = get();
            if (c == '/')
            {
                switch (peek())
                {
                    case '/':
                        {
                            for (; ; )
                            {
                                c = get();
                                if (c <= '\n')
                                {
                                    return c;
                                }
                            }
                        }
                    case '*':
                        {
                            get();
                            for (; ; )
                            {
                                switch (get())
                                {
                                    case '*':
                                        {
                                            if (peek() == '/')
                                            {
                                                get();
                                                return ' ';
                                            }
                                            break;
                                        }
                                    case EOF:
                                        {
                                            throw new Exception("Error: JSMIN Unterminated comment.\n");
                                        }
                                }
                            }
                        }
                    default:
                        {
                            return c;
                        }
                }
            }
            return c;
        }
      
        int peek()
        {
            theLookahead = get();
            return theLookahead;
        }
        
        int get()
        {
            int c = theLookahead;
            theLookahead = EOF;
            if (c == EOF)
            {
                c = sr.Read();
            }
            if (c >= ' ' || c == '\n' || c == EOF)
            {
                return c;
            }
            if (c == '\r')
            {
                return '\n';
            }
            return ' ';
        }
        void put(int c)
        {
            sw.Write((char)c);
        }
        
        bool isAlphanum(int c)
        {
            return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
                (c >= 'A' && c <= 'Z') || c == '_' || c == '$' || c == '\\' ||
                c > 126);
        }
        #endregion
    }
}

